package maosy.poi.util;

import maosy.poi.util.annotations.*;
import maosy.poi.util.exceptions.DataEmptyException;
import maosy.poi.util.exceptions.SheetNotExistException;
import maosy.poi.util.exceptions.SheetNotFoundException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.DataFormat;
import org.apache.poi.xssf.usermodel.*;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class ExcelUtil {

    /**
     * 读取 xlsx 文件
     *
     * @param workbook XSSFWorkbook
     * @param tClass   需要返回的数据实体类型
     * @return List<T>
     */
    public static <T> List<T> read4Book(XSSFWorkbook workbook, Class<T> tClass) throws SheetNotFoundException, InstantiationException, IllegalAccessException, ParseException {
        Sheet sheet = tClass.getAnnotation(Sheet.class);
        if (sheet == null) throw new SheetNotFoundException("annotation sheet not found.");
        org.apache.poi.ss.usermodel.Sheet sheetDoc = workbook.getSheet(sheet.name());
        if (sheetDoc == null)
            throw new SheetNotExistException("sheet name:" + sheet.name() + " was not existed in this workbook");
        return read(sheetDoc, tClass);
    }

    public static <T> XSSFWorkbook generator(XSSFWorkbook workbook, List<T> list) throws DataEmptyException, SheetNotFoundException, IllegalAccessException {
        if (list == null || list.isEmpty()) throw new DataEmptyException();
        String sheetName = sheetName(list.get(0));
        XSSFWorkbook work = workbook == null ? new XSSFWorkbook() : workbook;
        XSSFSheet sheet;
        int startRow;
        try {
            sheet = work.getSheet(sheetName);
            startRow = sheet.getLastRowNum() + 1;
        } catch (Exception e) {
            sheet = work.createSheet(sheetName);
            startRow = 0;
        }

        if (sheet == null) {
            sheet = work.createSheet(sheetName);
            startRow = 0;
        }

        List<String> titles = getTitles(list.get(0));
        if (!titles.isEmpty()) {
            genTitle(titles, sheet, startRow);
            startRow += 1;
        }
        genContent(list, sheet, startRow);
        return work;
    }

    private static <T> List<T> read(final org.apache.poi.ss.usermodel.Sheet sheet, Class<T> tClass) throws IllegalAccessException, InstantiationException, ParseException {
        XSSFRow titleRow = (XSSFRow) sheet.getRow(0);
        List<String> title = titles(titleRow);
        List<T> dataList = new ArrayList<>();
        for (int i = 1; i <= sheet.getLastRowNum(); i++) {
            XSSFRow dataRow = (XSSFRow) sheet.getRow(i);
            T t = tClass.newInstance();
            boolean hasData = true;
            for (int j = 0; j < dataRow.getLastCellNum(); j++) {
                XSSFCell cell = dataRow.getCell(j);
                if (cell == null) {
                    hasData = false;
                    break;
                }
                setData(cell, t, title.get(j));
            }
            if (hasData) dataList.add(t);
        }
        return dataList;
    }

    private static <T> void setData(XSSFCell cell, T t, String title) throws IllegalAccessException, ParseException {
        Field[] fields = t.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Title titleAnno = field.getAnnotation(Title.class);
            NumberPattern pattern = field.getAnnotation(NumberPattern.class);
            if (titleAnno == null || titleAnno.title().isEmpty()) continue;
            if (titleAnno.title().equals(title)) {
                if (field.getType().getSimpleName().equals("Integer") || field.getType().getSimpleName().equals("int")) {
                    field.set(t, Integer.valueOf("" + cell.getNumericCellValue()));
                }
                if (field.getType().getSimpleName().equals("Short") || field.getType().getSimpleName().equals("short")) {
                    field.set(t, Short.valueOf("" + cell.getNumericCellValue()));
                }
                if (field.getType().getSimpleName().equals("Long") || field.getType().getSimpleName().equals("long")) {
                    field.set(t, Long.valueOf("" + cell.getNumericCellValue()));
                }
                if (field.getType().getSimpleName().equals("Float") || field.getType().getSimpleName().equals("float")) {
                    field.set(t, Float.valueOf("" + cell.getNumericCellValue()));
                }
                if (field.getType().getSimpleName().equals("Double") || field.getType().getSimpleName().equals("double")) {
                    field.set(t, cell.getNumericCellValue());
                }
                if (field.getType().getSimpleName().equals("Boolean") || field.getType().getSimpleName().equals("boolean")) {
                    BooleanReplace replace = field.getAnnotation(BooleanReplace.class);
                    if (replace == null) {
                        field.set(t, cell.getBooleanCellValue());
                    } else {
                        if (cell.getStringCellValue().equals(replace.trueValue())) {
                            field.set(t, true);
                        } else {
                            field.set(t, false);
                        }
                    }
                }

                if (field.getType().getSimpleName().equals("Date")) {
                    if (cell.getCellType().equals(CellType.NUMERIC)) {
                        field.set(t, cell.getDateCellValue());
                    } else if (cell.getCellType().equals(CellType.STRING)) {
                        DateStyle dateStyle = field.getAnnotation(DateStyle.class);
                        SimpleDateFormat format;
                        if (dateStyle == null) format = new SimpleDateFormat("yyyy-MM-dd");
                        else format = new SimpleDateFormat(dateStyle.parent());
                        try {
                            field.set(t, format.parse(cell.getStringCellValue()));
                        } catch (IllegalArgumentException | IllegalAccessException | ParseException e) {
                            field.set(t, null);
                        }
                    }
                }
                if (field.getType().getSimpleName().equals("String")) {
                    if (cell.getCellType() == CellType.NUMERIC) {
                        DecimalFormat format;
                        if (pattern != null)
                            format = new DecimalFormat(pattern.pattern());
                        else
                            format = new DecimalFormat("0");
                        field.set(t, format.format(cell.getNumericCellValue()));
                    } else {
                        field.set(t, cell.getStringCellValue());
                    }

                }
            }
        }
    }

    private static List<String> titles(XSSFRow row) {
        final List<String> titles = new ArrayList<>();
        row.iterator().forEachRemaining(cell -> titles.add(cell.getStringCellValue()));
        return titles;
    }


    private static <T> void genContent(List<T> data, XSSFSheet sheet, int startRow) throws IllegalAccessException {
        int rowIndex = startRow;
        for (T t : data) {
            XSSFRow row = sheet.createRow(rowIndex);
            int columnIndex = 0;
            for (Field field : t.getClass().getDeclaredFields()) {
                field.setAccessible(true);

                Title title = field.getAnnotation(Title.class);
                if (title == null) continue;
                Object value = field.get(t);
                XSSFCell cell = row.createCell(columnIndex);

                if (value == null) cell.setCellValue("");
                if (value instanceof String) cell.setCellValue(value.toString());
                if (value instanceof Integer) cell.setCellValue(Double.parseDouble(value.toString()));
                if (value instanceof Long) cell.setCellValue(Double.parseDouble(value.toString()));
                if (value instanceof Float) cell.setCellValue(Double.parseDouble(value.toString()));
                if (value instanceof Double) cell.setCellValue(Double.parseDouble(value.toString()));
                if (value instanceof BigDecimal) cell.setCellValue(((BigDecimal) value).doubleValue());
                if (value instanceof Boolean) setBooleanValue(field, cell, (Boolean) value);
                if (value instanceof Date) {
                    cell.setCellStyle(genDateStyle(field, sheet.getWorkbook()));
                    cell.setCellValue((Date) value);
                }
                columnIndex++;
            }
            rowIndex++;
        }
    }

    private static <T> String sheetName(T t) throws SheetNotFoundException {
        Sheet sheet = t.getClass().getAnnotation(Sheet.class);
        if (sheet == null) throw new SheetNotFoundException();
        return sheet.name();
    }

    private static void setBooleanValue(Field field, XSSFCell cell, boolean value) {
        BooleanReplace replace = field.getAnnotation(BooleanReplace.class);
        if (replace == null) {
            cell.setCellValue(value);
        } else {
            if (value) {
                cell.setCellValue(replace.trueValue());
            } else {
                cell.setCellValue(replace.falseValue());
            }
        }
    }


    private static void genTitle(List<String> titles, XSSFSheet sheet, Integer startRow) {
        XSSFRow row = sheet.createRow(startRow);
        for (int i = 0; i < titles.size(); i++) {
            row.createCell(i).setCellValue(titles.get(i));
        }
    }

    private static XSSFCellStyle genDateStyle(Field field, XSSFWorkbook book) {
        DateStyle dateStyle = field.getAnnotation(DateStyle.class);
        XSSFCellStyle style = book.createCellStyle();
        DataFormat dataFormat = book.createDataFormat();
        if (dateStyle == null) {
            style.setDataFormat(dataFormat.getFormat("yyyy-MM-dd HH:mm:ss"));
        } else {
            style.setDataFormat(dataFormat.getFormat(dateStyle.parent()));
        }
        return style;
    }

    private static <T> List<String> getTitles(T t) throws SheetNotFoundException {
        List<String> titles = new ArrayList<>();
        Sheet sheet = t.getClass().getAnnotation(Sheet.class);
        if (sheet == null) throw new SheetNotFoundException();
        if (!sheet.needTitle()) return titles;
        for (Field field : t.getClass().getDeclaredFields()) {
            field.setAccessible(true);
            Title title = field.getAnnotation(Title.class);
            if (title == null) continue;
            titles.add(title.title());
        }
        return titles;
    }
}
